Game of Life 是英國數學家 John Horton Conway 在 1970 年發明的細胞自動機。
建立一個 LxW 的格子,每個格子代表一個細胞。
規則如下:
練習寫了一個生命遊戲,用的是 Javascript 和 D3.js,演算方法並沒有用最快的,所以速度無法加速到很快,改天再來換個演算法寫一次。也寫了幾個簡單的功能,分別是可以new、start/stop、加速和減速,以及點擊改變細胞狀態。
有個小地方 Debug 好久,是我對 JS 不夠熟悉:
var a = [];
a[0] = 5;
console.log(a[0]); //5
var b = a;
b[0] = 6;
console.log(a[0]); //6
var AA = [0,1];
var BB = [0,1];
console.log(AA == BB); //false
因為陣列算物件,所以 b
等於是和 a
是相通的。AA
和 BB
雖然看起來一樣,js 卻會判斷為 false
。
所以要比較 AA
和 BB
只能用:AA[0] == BB[0] && AA[1] == BB[1]
程式碼:
<!DOCTYPE html>
<html>
<head>
<title>Game of Life</title>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
</head>
<body>
<style>
button {
padding: 6px 12px;
font-size: 14px;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 4px;
margin: 5px;
}
.start {
color: white;
background-color: red;
border: 1px solid transparent;
}
.new {
color: white;
background-color: #5cb85c;
border: 1px solid transparent;
}
.speedUp {
color: white;
background-color: steelblue;
border: 1px solid transparent;
}
.speedDown {
color: white;
background-color: steelblue;
border: 1px solid transparent;
}
</style>
<p>Game of Life</p>
<div id="Control"></div>
<div id="Grids"></div>
<script>
var rectW = 30, rectL = 50, cc = 10, delayTime = 100, timerID = 0,
xs = d3.scale.linear().domain([0,rectL]).range([0,rectL * cc]),
ys = d3.scale.linear().domain([0,rectW]).range([0,rectW * cc]),
grids = [];
var stopped = false ;
var Neighbour = {
CLT: [[ 1, 0], [ 0, 1], [ 1, 1]],
CT: [[-1, 0], [ 1, 0], [-1, 1], [ 0, 1], [ 1, 1]],
CRT: [[-1, 0], [-1, 1], [ 0, 1]],
CL: [[ 0,-1], [ 1,-1], [ 1, 0], [ 0, 1], [ 1, 1]],
CR: [[-1,-1], [ 0,-1], [-1, 0], [-1, 1], [ 0, 1]],
CLB: [[ 0,-1], [ 1,-1], [ 1, 0]],
CB: [[-1,-1], [ 0,-1], [ 1,-1], [-1, 0], [ 1, 0]],
CRB: [[-1,-1], [ 0,-1], [-1, 0]],
C: [[-1,-1], [ 0,-1], [ 1,-1], [-1, 0], [ 1, 0], [-1, 1], [ 0, 1], [ 1, 1]]
};
var ctrl = d3.select("#Control")
ctrl.append('button')
.attr('class', 'new')
.text('New')
.on('click', function(){ return setNewGrid() });
ctrl.append('button')
.attr('class', 'start')
.text('Stop')
.on('click', startStop);
ctrl.append('button')
.attr('class', 'speedDown')
.text('◄◄')
.on('click', speedDown);
ctrl.append('button')
.attr('class', 'speedUp')
.text('►►')
.on('click', speedUp);
function speedUp(){
delayTime = delayTime / 2 ;
run();
}
function speedDown(){
delayTime = delayTime * 2;
run();
}
var rects = d3.select("#Grids")
.append("svg:svg")
.attr("stroke", "rgba(204, 204,204,0.2)")
.attr("shape-rendering", "crispEdges")
.attr("width",rectL*cc)
.attr("height", rectW*cc);
var initialize = function(){
grids = [];
d3.range(rectL).forEach(function(x){
grids[x] = new Array();
d3.range(rectW).forEach(function(y){
grids[x][y] = 0;
});
});
var GliderGun = [[24, 0],
[22, 1], [24, 1],
[12, 2], [13, 2], [20, 2], [21, 2], [34, 2], [35, 2],
[11, 3], [15, 3], [20, 3], [21, 3], [34, 3], [35, 3],
[0, 4], [1, 4], [10, 4], [16, 4], [20, 4], [21, 4],
[0, 5], [1, 5], [10, 5], [14, 5], [16, 5], [17, 5], [22, 5], [24, 5],
[10, 6], [16, 6], [24, 6],
[11, 7], [15, 7],
[12, 8], [13, 8]];
for(var i in GliderGun){
var _x = GliderGun[i][0]+5;
var _y = GliderGun[i][1]+10;
grids[_x][_y] = 1;
}
rects.selectAll("rect")
.data(function(){ return setGrid()})
.enter()
.append("rect")
.attr("x", function(d){ return xs(d.x)})
.attr("y", function(d){ return ys(d.y)})
.attr("width",cc)
.attr("height",cc)
.attr("fill", function(d){ return d.state? "black":"white";})
.on('click', function(){ return toggleGrid(d3.mouse(this)) });
};
var toggleGrid = function(xy){
var pos = [scaleDown(xy[0]), scaleDown(xy[1])];
grids[pos[0]][pos[1]] = grids[pos[0]][pos[1]]? 0: 1;
function scaleDown(n){
return Math.floor(n / cc);
};
displayGrid();
}
var setGrid = function (){
var bits = [];
for(var i= 0; i< rectL; i++){
for(var j= 0; j< rectW; j++){
bits.push({ "x": i, "y": j, "state": grids[i][j] });
}
}
return bits;
};
var setNewGrid = function (){
delayTime = 100;
initialize();
displayGrid();
}
var nextGen = function(){
var getNeighbours = function(x,y){
var nei = "C";
if (x == 0) nei += "L";
if (x == rectL-1) nei += "R";
if (y == 0) nei += "T";
if (y == rectW-1) nei += "B";
return Neighbour[nei];
};
var _grids = [];
for(var i= 0; i< rectL; i++){
_grids[i] = [];
for(var j= 0; j< rectW; j++){
var liveNei = 0, thisGrid = grids[i][j], newGrid;
var Nei = getNeighbours(i,j);
for(var k in Nei){
liveNei += (grids[i+Nei[k][0]][j+Nei[k][1]]? 1: 0);
}
if(thisGrid){//alive
newGrid = liveNei==2 || liveNei==3 ? 1: 0;
}else{
newGrid = liveNei ==3 ? 1: 0;
}
_grids[i][j] = newGrid;
}
}
grids = _grids;
};
var displayGrid = function(){
rects.selectAll("rect")
.data(function(){ return setGrid()})
.transition()
.attr("fill", function(d) { return d.state ? "black" : "white" })
.duration(0);
}
function startStop() {
stopped = !stopped;
d3.select('button.start')
.text(function () { return stopped ? 'Start' : 'Stop';});
}
var run = function(){
var aRound = function(){
if (!stopped) {
nextGen();
displayGrid();
}
}
clearInterval(timerID);
timerID = setInterval(function(){return aRound();},delayTime);
}
function firstRun(){
initialize();
run();
};
firstRun();
</script>
</body>
</html>